use diesel::prelude::*;
use json_patch::Patch;
use redis_macros::FromRedisValue;
use rust_embed::Embed;
use salvo::oapi::{Components, Object, RefOr, ToSchema, schema};
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, ToSchema)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
    pub id: i64,
    pub username: String,
    #[serde(skip)]
    pub password: String,
    pub avatar: String,
}

#[derive(Debug, Clone, Insertable, Deserialize, ToSchema)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct UserToAdd {
    pub username: String,
    pub password: String,
}

#[derive(Debug, Clone, AsChangeset, Identifiable, Deserialize, ToSchema)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct UserToUpd {
    pub id: i64,
    pub avatar: Option<String>,
}

#[derive(Debug, Clone, AsChangeset, Identifiable, Deserialize, ToSchema)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct UserToUpdPass {
    pub id: i64,
    // #[diesel(skip_update)]
    // pub password_old: String,
    pub password: String,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct UserToLis {
    pub username: Option<String>,
}

#[derive(
    Debug,
    Clone,
    Queryable,
    Selectable,
    Serialize,
    Deserialize,
    ToSchema,
    Associations,
    FromRedisValue,
)]
#[diesel(belongs_to(User, foreign_key = owner_id))]
#[diesel(table_name = crate::schema::spaces)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Space {
    pub id: i64,
    pub owner_id: i64,
    pub name: String,
    pub desp: Option<String>,
    pub booking: bool,
    pub state: Value,
    pub state_ver: i64,
}

#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, ToSchema, FromRedisValue)]
#[diesel(belongs_to(User))]
#[diesel(table_name = crate::schema::spaces)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct SpaceBasic {
    pub id: i64,
    pub owner_id: i64,
    pub name: String,
    pub desp: Option<String>,
    pub booking: bool,
}

#[derive(Debug, Clone, Insertable)]
#[diesel(table_name = crate::schema::spaces)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct SpaceToAdd {
    pub owner_id: i64,
    pub name: String,
    pub desp: Option<String>,
    pub booking: bool,
    pub state: Value,
}

#[derive(Debug, Clone, AsChangeset, Identifiable, Deserialize, ToSchema)]
#[diesel(table_name = crate::schema::spaces)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct SpaceToUpd {
    pub id: i64,
    pub name: Option<String>,
    pub desp: Option<String>,
    pub booking: Option<bool>,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct SpaceToLis {
    pub owner_id: Option<i64>,
    pub name: Option<String>,
    pub desp: Option<String>,
    pub booking: Option<bool>,
}

#[derive(Debug, Clone, Queryable, Selectable, Serialize, ToSchema, Associations)]
#[diesel(belongs_to(Space))]
#[diesel(belongs_to(User))]
#[diesel(table_name = crate::schema::bookings)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Booking {
    pub space_id: i64,
    pub user_id: i64,
    pub status: bool,
}

#[derive(Debug, Clone, Insertable, Deserialize, ToSchema)]
#[diesel(table_name = crate::schema::bookings)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct BookingToAdd {
    pub space_id: i64,
    pub user_id: i64,
}

#[derive(Debug, Clone, AsChangeset, Identifiable, Deserialize, ToSchema)]
#[diesel(primary_key(space_id, user_id))]
#[diesel(table_name = crate::schema::bookings)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct BookingToUpd {
    pub space_id: i64,
    pub user_id: i64,
    pub status: Option<bool>,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct BookingToLis {
    pub space_id: Option<i64>,
    pub user_id: Option<i64>,
    pub status: Option<bool>,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct LisRes<T>
where
    T: Serialize + ToSchema,
{
    pub total: i64,
    pub data: Vec<T>,
}

#[derive(Embed)]
#[folder = "assets"]
pub struct Asset;

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, FromRedisValue)]
pub struct Rect {
    pub x: f64,
    pub y: f64,
    pub w: f64,
    pub h: f64,
    pub scale: f64,
}

impl Default for Rect {
    fn default() -> Self {
        Self {
            x: 0_f64,
            y: 0_f64,
            w: 800_f64,
            h: 600_f64,
            scale: 1_f64,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, FromRedisValue)]
pub struct Panel {
    pub id: String,
    pub kind: String,
    pub label: Option<String>,
    pub rect: Rect,
    pub content: Value,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, FromRedisValue)]
pub struct Board {
    pub id: String,
    pub label: Option<String>,
    pub rect: Rect,
    pub panels: Vec<Panel>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, FromRedisValue)]
pub struct SpaceState {
    pub boards: Vec<Board>,
    pub active_id: Option<String>,
}

#[derive(Serialize, Deserialize, ToSchema)]
pub struct PayloadPerm {
    pub user_id: i64,
    pub perm: bool,
}

#[derive(Serialize, Deserialize)]
pub struct PayloadPatch {
    pub version: Option<i64>,
    pub patch: Patch,
}

impl ToSchema for PayloadPatch {
    fn to_schema(_components: &mut Components) -> RefOr<schema::Schema> {
        Object::new()
            .property("patch", Object::new())
            .required("patch")
            .into()
    }
}
